Extract interface ResourceDao from ResourceDaoBean
Update Test
Rename ResourceDaoTest --> ResourceDaoBeanTest
Remove Base Class
Remove dao attribute
Rewrite getDao() to return a looked up ResourceDao
Add method with @BeforeClass annotation that initializes the container
Run Your Tests (ResourceDaoBeanTest): First Failures
After making these changes, we have 3 tests that pass and one that fails. The error in the JUnit stack trace looks like this: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: entity.Book.authors, no session or session was closed
If you double-click on the last line listed in the stack trace, it will show the last line, line 9, as the problem line. When we retrieve the authors() from found, there's no problem -- yet. When we ask the object returned from getAuthors() for its size(), we're accessing not a collection but a proxy to a collection. Remember that the object found is detached. Why? We are no longer in a method in the container, we have returned from the method. When we returned, the transaction committed and all objects in the persistence context were detached. The author’s relationship is lazily loaded by default (because it is a @ManyToMany and that's the default behavior).
We have three ways to fix this problem:
Use Eager fetching
Directly access the collection while still in the session bean method to get it initialized (read in)
Change the test to send a message to the ResourceDao to ask for the number of authors associated with a particular book
We'll take the easiest way out to fix this and make this relationship eagerly fetched rather than lazily fetched. Here's the change to Book.java:
The change there is adding fetch = FetchType.EAGER
Try #2
When you run this, things still do not work. This is a bi-directional relationship. However, while we are adding an author to the book, we are not adding the book to the author. Remember that we must maintain both sides of a bi-directional relationship. Update the addAuthor() method in book to add the book to the author:
We're close. The problem is since we started properly maintaining a bi-directional relationship between books and authors; JPA is automatically inserting two foreign keys in to a join table called AUTHOR_BOOK. Well to remove the book, we need to remove the relationship between the book and the authors (both sides).
Here is the challenge. We have one dao, ResourceDao, which removes both books and dvd's as well as all kinds of resources. The book has a dependency on Author that not all resources have. So how can we still use the ResourceDao to remove a book if the book has specific logic? Here are two options:
Use type-checking in the ResourceDao to do custom delete logic based on type
Have the resource dao delegate a message to all resources polymorphically, the book will take care of specific clean-up logic
Type checking is not always bad, just mostly always. We won't even consider that because polymorphism is the way to go here. Here are the three steps we're going to follow:
Create an abstract method in Resource called remove().
Add an empty implementation of this method to Dvd so it will compile
Add an implementation into Book to clean up all of its relationships
Add any required supporting methods in other classes
Make sure to actually call the remove method in the ResourceDao
Call the remove() method in ResourceDao
Just after looking up the resource and just before actually removing it, we need to call the remove() method on the Resource object:
Now that all of our tests in ResourceDaoBeanTest pass, we need to clean up after ourselves. Here are the stats:
Table
Rows
Author
7
Book
2
Author_Book
5
Resource
2
We need to update each of the tests that create books and explicitly remove the books and authors created. It turns out we do not need to explicitly remove anything from Author_Book. Just updating the bi-directional relationships properly will fix that problem.
Delete Author
To delete authors, we'll create a new Dao for Authors: AuthorDao
Support Methods
First we need a few methods we can use to delete authors and books (we're working in ResourceDaoBeanTest, so this is a fine place to add these methods):
These methods are static because we might want to use them from other tests. However, to make them static, we must change getDao() to be a static method as well.
Updated Tests
Here are the updated tests that now use the support methods.
@Test
publicvoid createABook(){finalBook b = createABookImpl();try{final Resource found = getDao().retrieve(b.getId());
assertNotNull(found);}finally{
removeBookAndAuthors(b);}}
@Test
publicvoid removeABook(){finalBook b = createABookImpl();try{
Resource found = getDao().retrieve(b.getId());
assertNotNull(found);
getDao().remove(b.getId());
found = getDao().retrieve(b.getId());
assertNull(found);}finally{
removeAuthors(b.getAuthors());}}
@Test
publicvoid updateABook(){Book b = createABookImpl();try{finalint initialAuthorCount = b.getAuthors().size();
b.addAuthor(new Author(newName("New", "Author")));
getDao().update(b);
b = (Book) getDao().retrieve(b.getId());
assertEquals(initialAuthorCount + 1, b.getAuthors().size());}finally{
removeBookAndAuthors(b);}}
Notice that we re-assign the variable b just before the assert equals and it is that updated version of b that is sent to removeBookAndAuthors(). Why do you suppose we need to do that?
At this point you might want to go back and verify that the tests in PatronDaoBeanTest still pass.
Update Dao
Update Test
Run Your Tests (ResourceDaoBeanTest): First Failures
After making these changes, we have 3 tests that pass and one that fails. The error in the JUnit stack trace looks like this: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: entity.Book.authors, no session or session was closedHere's the actual test:
If you double-click on the last line listed in the stack trace, it will show the last line, line 9, as the problem line. When we retrieve the authors() from found, there's no problem -- yet. When we ask the object returned from getAuthors() for its size(), we're accessing not a collection but a proxy to a collection. Remember that the object found is detached. Why? We are no longer in a method in the container, we have returned from the method. When we returned, the transaction committed and all objects in the persistence context were detached. The author’s relationship is lazily loaded by default (because it is a @ManyToMany and that's the default behavior).
We have three ways to fix this problem:
We'll take the easiest way out to fix this and make this relationship eagerly fetched rather than lazily fetched. Here's the change to Book.java:
The change there is adding fetch = FetchType.EAGER
Try #2
When you run this, things still do not work. This is a bi-directional relationship. However, while we are adding an author to the book, we are not adding the book to the author. Remember that we must maintain both sides of a bi-directional relationship. Update the addAuthor() method in book to add the book to the author:Run the tests in ResourceDaoBeanTest. Now more tests are failing. This is getting worse before it gets better.
Nearly Finally Fixed
There's a method in Author.java that looks like this:
The problem is, the booksWritten attribute is never assigned. Here's a way to fix this:
Finally All of ResourceDaoBaseTest Running
We're close. The problem is since we started properly maintaining a bi-directional relationship between books and authors; JPA is automatically inserting two foreign keys in to a join table called AUTHOR_BOOK. Well to remove the book, we need to remove the relationship between the book and the authors (both sides).
Here is the challenge. We have one dao, ResourceDao, which removes both books and dvd's as well as all kinds of resources. The book has a dependency on Author that not all resources have. So how can we still use the ResourceDao to remove a book if the book has specific logic? Here are two options:
Type checking is not always bad, just mostly always. We won't even consider that because polymorphism is the way to go here. Here are the three steps we're going to follow:
Add abstract method to Resource
Add empty implementation to Dvd
Add implementation to Book
Add removeBook to Author
Call the remove() method in ResourceDao
Just after looking up the resource and just before actually removing it, we need to call the remove() method on the Resource object:
Run the tests and they now all pass.
Test Isolation
Now that all of our tests in ResourceDaoBeanTest pass, we need to clean up after ourselves. Here are the stats:
We need to update each of the tests that create books and explicitly remove the books and authors created. It turns out we do not need to explicitly remove anything from Author_Book. Just updating the bi-directional relationships properly will fix that problem.
Delete Author
To delete authors, we'll create a new Dao for Authors:AuthorDao
AuthorDaoBean
Update Test
Support MethodsFirst we need a few methods we can use to delete authors and books (we're working in ResourceDaoBeanTest, so this is a fine place to add these methods):
These methods are static because we might want to use them from other tests. However, to make them static, we must change getDao() to be a static method as well.
Updated Tests
Here are the updated tests that now use the support methods.
@Test public void createABook() { final Book b = createABookImpl(); try { final Resource found = getDao().retrieve(b.getId()); assertNotNull(found); } finally { removeBookAndAuthors(b); } } @Test public void removeABook() { final Book b = createABookImpl(); try { Resource found = getDao().retrieve(b.getId()); assertNotNull(found); getDao().remove(b.getId()); found = getDao().retrieve(b.getId()); assertNull(found); } finally { removeAuthors(b.getAuthors()); } } @Test public void updateABook() { Book b = createABookImpl(); try { final int initialAuthorCount = b.getAuthors().size(); b.addAuthor(new Author(new Name("New", "Author"))); getDao().update(b); b = (Book) getDao().retrieve(b.getId()); assertEquals(initialAuthorCount + 1, b.getAuthors().size()); } finally { removeBookAndAuthors(b); } }Notice that we re-assign the variable b just before the assert equals and it is that updated version of b that is sent to removeBookAndAuthors(). Why do you suppose we need to do that?
At this point you might want to go back and verify that the tests in PatronDaoBeanTest still pass.